package org.zn.note.mybatis.setting;

import org.apache.ibatis.executor.CachingExecutor;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.defaults.DefaultSqlSession;
import org.zn.note.mybatis.domain.CmMerchant;
import org.zn.note.mybatis.mapper.CmMerchantMapper;
import org.zn.note.mybatis.util.MyBatisUtils;

import java.util.Scanner;

/**
 * MyBatis 缓存
 * <p>
 * 一级缓存（local cache）
 * 默认开启，不能控制。一般说“mybatis缓存”都是指二级缓存。
 * 修改一级缓存作用域，默认SESSION。<setting name="localCacheScope" value="SESSION"/>  (SESSION | STATEMENT)
 * SESSION级别：
 * 同一个SqlSession对象(只要是从同一个sqlSession获得的mapper)，执行都走同一个一级缓存。
 * 注意！每次从缓存返回的是同一个对象的引用，不要直接修改返回的对象！
 * 1、同一session下，所有mapper共享这个缓存。（无论哪个mapper执行相同的SQL，都返回同一个POJO对象）
 * 2、结束事务(session.commit or rollback)，清空缓存。下次再查，会走数据库。
 * 3、同一session下，insert/update/delete，清空缓存。下次再查，会走数据库。
 * （其实缓存都是通过select存储的，insert/update/delete/commit/rollback 都是 clearCache）
 * STATEMENT级别：
 * 每次都清空一级缓存。（其实效果等同于取消一级缓存了）
 * 在分布式的应用中（或多session），建议设置为 STATEMENT (不用一级缓存)，避免脏数据
 * <p>
 * 二级缓存（second level cache）
 * 如果开启了，命中顺序：二级缓存 > 一级缓存 > 数据库
 * 一级缓存最大的共享范围是session内部，二级缓存是在多个session间共享。
 * 如果用二级缓存了，Executor的实现就变为 {@link CachingExecutor}，
 * 他是对Executor的包装，在这里面处理了二级缓存，然后再交给Executor继续执行。
 * 开启前：SQL --> Executor（LocalCache） --> DB
 * 开启后：SQL --> CachingExecutor(全局 namespace:Cache, namespace:Cache, ...) --> Executor（LocalCache） --> DB
 * 二级缓存配置：
 * 1、MyBatis Config 里 <setting name="cacheEnabled" value="true"/> 开启二级缓存
 * 2、Mapper xml 里 <cache/> 声明这个namespace开启二级缓存； 也可以<cache-ref/>表示两个命名空间共用一个cache。
 * 3、POJO必须是 Serializable
 * <p>
 * {@link SqlSessionFactory#openSession}  每次返回不同的 SqlSession对象
 * 一个sqlSession，就是一个connection，不一定是新建的，可能是从一个池子里拿的，但能保证是一个connnection。
 * {@link SqlSession#getMapper}  每次返回不同的 Mapper对象，即使入参是相同的类
 * SqlSession默认实现 {@link DefaultSqlSession}
 * <p>
 * 最终：生产环境，现在都是分布式的，最好关了mybatis的缓存机制，单纯作为ORM工具使用。
 */
public class MyCache {

    // 建议全局作用域
    private static SqlSessionFactory sqlSessionFactory = MyBatisUtils.createSqlSessionFactoryByXml();

    // Mapper 应该是方法作用域，这里为了方便测试，放到这了
    private static CmMerchantMapper session1_mapper1;
    private static CmMerchantMapper session1_mapper2;
    private static CmMerchantMapper session2_mapper1;
    private static CmMerchantMapper session2_mapper2;

    private static void testCache() {

        Configuration config = sqlSessionFactory.getConfiguration();
        System.out.printf("Configuration setting [%s] [%s]\n", "localCacheScope", config.getLocalCacheScope());

        try (
                Scanner scanner = new Scanner(System.in);
                SqlSession sqlSession1 = sqlSessionFactory.openSession(false);
                SqlSession sqlSession2 = sqlSessionFactory.openSession(false);
        ) {

            System.out.printf("\n----\n 每次openSession返回不同session对象：hashCode1[%s], hashCode2[%s] \n----\n",
                    sqlSession1.hashCode(), sqlSession2.hashCode());

            session1_mapper1 = sqlSession1.getMapper(CmMerchantMapper.class);
            session1_mapper2 = sqlSession1.getMapper(CmMerchantMapper.class);
            session2_mapper1 = sqlSession2.getMapper(CmMerchantMapper.class);
            session2_mapper2 = sqlSession2.getMapper(CmMerchantMapper.class);

            System.out.printf("\n----\n 每次getMapper返回不同的mapper对象：[%s] [%s] [%s] [%s] \n----\n",
                    session1_mapper1.hashCode(), session1_mapper2.hashCode(),
                    session2_mapper1.hashCode(), session2_mapper2.hashCode());

            // session1-mapper1 执行 select，缓存了
            System.out.printf("\n----\n session1_mapper1.selectByPrimaryKey() \n----\n");
            CmMerchant cmMerchant = session1_mapper1.selectByPrimaryKey(1);
            System.out.printf("\n----\n session1_mapper1: merchant hash[%s], merchantName[%s] \n----\n",
                    cmMerchant.hashCode(), cmMerchant.getMerchantName());

            // 测试 一级缓存 localCacheScope=STATEMENT，在用相同的mapper执行一次
//            System.out.printf("\n----\n session1_mapper1.selectByPrimaryKey() \n----\n");
//            cmMerchant = session1_mapper1.selectByPrimaryKey(1);
//            System.out.printf("\n----\n session1_mapper1: merchant hash[%s], merchantName[%s] \n----\n",
//                    cmMerchant.hashCode(), cmMerchant.getMerchantName());

            // 手动控制，修改数据库记录
            System.out.printf("\n----\n 手工修改数据库 \n----\n");
            scanner.nextLine();

            // 关闭事务可以清空缓存
            System.out.printf("\n----\n sqlSession1.commit() \n----\n");
            sqlSession1.commit();

            // 手动修改数据，不刷新session1缓存，不会查出最新的值
            printSelect();
            scanner.nextLine();

            // session2修改数据，不刷新session1缓存，session1查不到新值
            CmMerchant cmMerchantUpd = new CmMerchant();
            cmMerchantUpd.setId(1);
            cmMerchantUpd.setMerchantName("aaa");
            System.out.printf("\n----\n session2_mapper1.updateByPrimaryKeySelective() \n----\n");
            session2_mapper1.updateByPrimaryKeySelective(cmMerchantUpd);
            sqlSession2.commit();
            printSelect();
            scanner.nextLine();

            // session1修改数据，刷新session1缓存，session1可以查出最新值，但是session2走他的缓存了查不到新值
            cmMerchant.setMerchantName("bbb");
            System.out.printf("\n----\n session1_mapper2.updateByPrimaryKeySelective() \n----\n");
            session1_mapper2.updateByPrimaryKeySelective(cmMerchant);
            sqlSession1.commit();
            printSelect();
            scanner.nextLine();
        }
    }

    private static void printSelect() {
        System.out.printf("\n----\n session1_mapper1.selectByPrimaryKey() \n----\n");
        CmMerchant cmMerchant1 = session1_mapper1.selectByPrimaryKey(1);
        System.out.printf("\n----\n session1_mapper2.selectByPrimaryKey() \n----\n");
        CmMerchant cmMerchant2 = session1_mapper2.selectByPrimaryKey(1);
        System.out.printf("\n----\n session2_mapper1.selectByPrimaryKey() \n----\n");
        CmMerchant cmMerchant3 = session2_mapper1.selectByPrimaryKey(1);
        System.out.printf("\n----\n session2_mapper2.selectByPrimaryKey() \n----\n");
        CmMerchant cmMerchant4 = session2_mapper2.selectByPrimaryKey(1);
        System.out.printf("\n----\n" +
                        " session1_mapper1: merchant hash[%s], merchantName[%s]\n" +
                        " session1_mapper2: merchant hash[%s], merchantName[%s]\n" +
                        " session2_mapper1: merchant hash[%s], merchantName[%s]\n" +
                        " session2_mapper2: merchant hash[%s], merchantName[%s]" +
                        " \n----\n",
                cmMerchant1.hashCode(), cmMerchant1.getMerchantName(),
                cmMerchant2.hashCode(), cmMerchant2.getMerchantName(),
                cmMerchant3.hashCode(), cmMerchant3.getMerchantName(),
                cmMerchant4.hashCode(), cmMerchant4.getMerchantName());
    }

    public static void main(String[] args) {
        testCache();
    }
}
